﻿namespace NTLib.Pipe.Containers
{
    using NTLib.Core.Args;
    using NTLib.Core.Pool;
    using NTLib.Core.Reflection;
    using NTLib.Core.Threading;
    using NTLib.Pipe.Descriptors;
    using NTLib.Pipe.Exceptions;
    using NTLib.Pipe.Targets;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;

    /// <summary>
    /// Base class that implement the base of IPipe<T> but also the Pool Item patern.
    /// </summary>
    public abstract class BasePipeSourcePoolItem<T, TargetType> : BasePipePoolItem, IPipeSource<T>
        where TargetType : ITargetDescriptor
    {
        #region Fields

        private readonly SemaphoreSlim _lockAccess;

        protected bool _isInitialized;
        protected bool _isInitializing;
        private Guid _currentId;
        private TargetType _targetDesc;

        #endregion

        #region Ctor

        /// <summary>
        /// Initializes a new instance of the <see cref="MemoryPipeSource{T}"/> class.
        /// </summary>
        public BasePipeSourcePoolItem()
        {
            this._currentId = Guid.NewGuid();
            this._lockAccess = new SemaphoreSlim(1);
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets a value indicating whether this instance can pop item.
        /// </summary>
        /// <value>
        ///   <c>true</c> if this instance can pop item; otherwise, <c>false</c>.
        /// </value>
        public abstract bool CanPopItem { get; }

        /// <summary>
        /// Gets the value indicating that the current pipe allow the user to push items through it.
        /// </summary>
        /// <value>s
        ///   <c>true</c> if this instance can push item; otherwise, <c>false</c>.
        /// </value>
        public abstract bool CanPushItem { get; }

        /// <summary>
        /// Gets the unique ID.
        /// </summary>
        public override Guid Id
        {
            get { return this._currentId; }
        }

        /// <summary>
        /// Gets the other side target information.
        /// </summary>
        /// <remarks>The target null define that the pipe is not attach yet.</remarks>
        public ITargetDescriptor Target
        {
            get
            {
                CheckSourceIsInitialized();
                return this._targetDesc;
            }
        }

        /// <summary>
        /// Gets the target typed.
        /// </summary>
        protected TargetType TargetTyped
        {
            get { return this._targetDesc; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is initialize.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is initialize; otherwise, <c>false</c>.
        /// </value>
        protected bool IsInitialize
        {
            get { return this._isInitialized; }
        }

        #endregion

        #region Event

        /// <summary>
        /// Occurs when some data have been received on the pipe.
        /// </summary>
        public event Action DataReceived;

        #endregion

        #region Methods

        /// <summary>
        /// Initializes the current pipe asynchronously.
        /// </summary>
        /// <returns>Task used to make this initialization asynchronously.</returns>
        public async Task InitializeAsync(ITargetDescriptor desc, PipeType type, CancellationToken token = default(CancellationToken))
        {
            if (this._isInitializing)
                throw new PipeInitializingException();

            if (this.Status == PipeStatus.Ready || this._isInitialized)
                throw new PipeAlreadyInitializedException();

            if (!desc.IsOrInheritOf(typeof(TargetType)))
                throw new InvalidTargetTypeException(this.Target.GetType(), typeof(TargetType));

            this._isInitializing = true;

            try
            {
                this._targetDesc = (TargetType)desc;
                this.PipeType = type;
                await OnInitializeAsync(token);
                this.Status = PipeStatus.Ready;
                this._isInitialized = true;

            }
            finally
            {
                this._isInitializing = false;
            }
        }

        /// <summary>
        /// Called when the update pop waiter is called.
        /// This methods is used to dequeue item from the queue to the waiter.
        /// </summary>
        /// <returns></returns>
        /// <exception cref="System.NotImplementedException"></exception>
        public async Task<T> DequeueItemAsync(CancellationToken token = default(CancellationToken))
        {
            CheckSourceIsInitialized();
            if (!this.CanPopItem)
                throw new PipeActionNotPermitsException(this.PipeType, "DequeueItem");

            using (await this._lockAccess.ScopeWaitAsync(token))
            {
                token.ThrowIfCancellationRequested();
                return await OnDequeueItemAsync(token);
            }
        }

        /// <summary>
        /// Pushes the item on the storage system without waiting any response.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        /// <exception cref="PipeActionNotPermitsException">SendItemAsync</exception>
        public async Task SendItemAsync(T item, CancellationToken token = default(CancellationToken))
        {
            CheckSourceIsInitialized();
            if (!this.CanPushItem)
                throw new PipeActionNotPermitsException(this.PipeType, nameof(SendItemAsync));

            using (await this._lockAccess.ScopeWaitAsync(token))
            {
                await OnSend(item, token);
            }
        }

        /// <summary>
        /// Initializes the current pipe asynchronously.
        /// </summary>
        /// <returns>Task used to make this initialization asynchronously.</returns>
        protected virtual async Task OnInitializeAsync(CancellationToken token = default(CancellationToken))
        {
            await Task.Yield();
        }

        /// <summary>
        /// Called when the pool item is reset.
        /// Everything MUST be initialized on this method.
        /// </summary>
        protected sealed override void OnReset()
        {
            this._currentId = Guid.NewGuid();
            this._isInitialized = false;
            base.OnReset();
            OnPoolItemReset();
        }

        /// <summary>
        /// Called when the pool item is reset.
        /// Everything MUST be initialized on this method.
        /// </summary>
        protected virtual void OnPoolItemReset()
        {
        }

        /// <summary>
        /// Called when the update pop waiter is called.
        /// This methods is used to dequeue item from the queue to the waiter.
        /// This calls is Thread Safe.
        /// </summary>
        protected abstract Task<T> OnDequeueItemAsync(CancellationToken token);

        /// <summary>
        /// Pushes the item on the storage system without waiting any response.
        /// </summary>
        protected abstract Task OnSend(T item, CancellationToken token);

        /// <summary>
        /// Checks the source is initialized.
        /// </summary>
        /// <exception cref="PipeNotInitializedException"></exception>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        protected void CheckSourceIsInitialized()
        {
            if (!this._isInitialized)
                throw new PipeNotInitializedException();
        }

        /// <summary>
        /// Manageds the dispose.
        /// </summary>
        protected override void ManagedDispose()
        {
            this._isInitialized = false;
            this._currentId = Guid.Empty;
            base.ManagedDispose();
        }

        /// <summary>
        /// Raises the data received.
        /// </summary>
        protected virtual void RaiseDataReceived()
        {
            if (this.DataReceived != null)
                this.DataReceived();
        }


        #endregion
    }
}
